﻿using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using CoreEx.Common.Cache;
using CoreEx.Common.Extensions;
using CoreEx.DynamicInvoker;
using LinFu.AOP.Interfaces;
using LinFu.IoC;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.Proxy.Interfaces;

namespace CoreEx.ServiceModel.Implementation
{
    /// <summary>
    /// Creates a service proxy that intercepts
    /// a service contract type and forwards calls to a <see cref="IChannel"/> instance.
    /// </summary>    
    [Implements(typeof(IClientServiceProxyFactory), LifecycleType.Singleton)]
    public class ClientServiceProxyFactory : IClientServiceProxyFactory, IInitialize
    {
        private ILazyCache<Uri, ChannelFactory> _cache;

        private IServiceContainer _serviceContainer;

        private readonly object _syncObject = new object();

        /// <summary>
        /// Gets or sets the <see cref="IProxyFactory"/> instance 
        /// that is responsible for creating service proxies.
        /// </summary>
        public IProxyFactory ProxyFactory { get; set; }

        /// <summary>
        /// Gets the <see cref="IBindingProvider"/> that is responsible 
        /// for creating an instance of a class that inherits from the <see cref="Binding"/> class.
        /// </summary>
        public IBindingProvider BindingProvider { get; private set; }


        /// <summary>
        /// Gets the <see cref="IServiceContractInterceptorFactory"/> that is responsible 
        /// for creating <see cref="ServiceContractClientInterceptor{TChannel}"/> instances.
        /// </summary>
        public IServiceContractInterceptorFactory ServiceContractInterceptorFactory { get; private set; }

        /// <summary>
        /// Gets a list of <see cref="IInitializer{TTarget}"/> that is responsible for initializing 
        /// the <see cref="ServiceEndpoint"/>.
        /// </summary>
        public IEnumerable<IInitializer<ServiceEndpoint>> ServiceEndPointInitializers { get; private set; }


        /// <summary>
        /// Creates a service contract proxy of the specified type that is used to communicate
        /// with a WCF service.
        /// </summary>
        /// <param name="serviceType">The service contract type to intercept.</param>
        /// <param name="uri">The <see cref="Uri"/> that represents the service endpoint address.</param>
        /// <returns>A service contract proxy.</returns>
        public object CreateServiceProxy(Type serviceType, Uri uri)
        {
            lock (_syncObject)
            {
                                                                                
                //Get the IInterceptor instance that is responsible for creating and closing 
                //channels when methods are invoked.
                IInterceptor serviceContractInterceptor = GetServiceContractInterceptor(uri, serviceType);

                //Create the service proxy
                var serviceProxy = ProxyFactory.CreateProxy(serviceType, serviceContractInterceptor);

                return serviceProxy;
            }
        }


        private IInterceptor GetServiceContractInterceptor(Uri uri, Type serviceType)
        {
            var channelFactory = GetChannelFactory(uri, serviceType);

            return ServiceContractInterceptorFactory.CreateServiceContractInterceptor(channelFactory, serviceType);
        }

        private ChannelFactory GetChannelFactory(Uri uri, Type serviceType)
        {
            return _cache.GetValue(uri,u => CreateChannelFactory(serviceType,u));
        }


        private ChannelFactory CreateChannelFactory(Type serviceType, Uri uri)
        {
                                                
            Binding binding = BindingProvider.CreateBinding(serviceType,uri);
            
            var endpointAddress = new EndpointAddress(uri);

            var channelFactoryType = typeof(ChannelFactory<>).MakeGenericType(serviceType);

            var channelFactory = (ChannelFactory)Activator.CreateInstance(channelFactoryType, new object[] { binding, endpointAddress });

            //Get a reference to the service end point 
            var serviceEndPoint = (ServiceEndpoint)channelFactoryType.DynamicInvoke(channelFactory, "Endpoint");

            InitializeServiceEndpoint(serviceEndPoint);
            
            return channelFactory;
        }


        private void InitializeServiceEndpoint(ServiceEndpoint serviceEndPoint)
        {
            ServiceEndPointInitializers.ForEach(si => si.Initialize(serviceEndPoint));
        }

        /// <summary>
        /// Initializes this service instance-
        /// </summary>        
        public void Initialize(IServiceContainer source)
        {
            _serviceContainer = source;
            ProxyFactory = source.GetService<IProxyFactory>();
            BindingProvider = source.GetService<IBindingProvider>();            
            ServiceContractInterceptorFactory = source.GetService<IServiceContractInterceptorFactory>();
            ServiceEndPointInitializers = _serviceContainer.GetServices<IInitializer<ServiceEndpoint>>();            
            _cache = source.GetService<ILazyCache<Uri, ChannelFactory>>();
        }

    }
}